From 243404bf34b1470369e1d0f5f2dd18ac02435273 Mon Sep 17 00:00:00 2001 From: german77 Date: Fri, 16 Dec 2022 16:16:54 -0600 Subject: input_common: Add virtual gamepad --- src/core/hid/emulated_controller.cpp | 82 ++++++++++++++++++++++++++++ src/core/hid/emulated_controller.h | 9 +++ src/input_common/CMakeLists.txt | 2 + src/input_common/drivers/virtual_gamepad.cpp | 78 ++++++++++++++++++++++++++ src/input_common/drivers/virtual_gamepad.h | 73 +++++++++++++++++++++++++ src/input_common/main.cpp | 23 ++++++++ src/input_common/main.h | 7 +++ 7 files changed, 274 insertions(+) create mode 100644 src/input_common/drivers/virtual_gamepad.cpp create mode 100644 src/input_common/drivers/virtual_gamepad.h diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 67969e938..f238d6ccd 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -145,6 +145,7 @@ void EmulatedController::LoadDevices() { output_params[3].Set("output", true); LoadTASParams(); + LoadVirtualGamepadParams(); std::ranges::transform(button_params, button_devices.begin(), Common::Input::CreateInputDevice); std::ranges::transform(stick_params, stick_devices.begin(), Common::Input::CreateInputDevice); @@ -163,6 +164,12 @@ void EmulatedController::LoadDevices() { Common::Input::CreateInputDevice); std::ranges::transform(tas_stick_params, tas_stick_devices.begin(), Common::Input::CreateInputDevice); + + // Initialize virtual gamepad devices + std::ranges::transform(virtual_button_params, virtual_button_devices.begin(), + Common::Input::CreateInputDevice); + std::ranges::transform(virtual_stick_params, virtual_stick_devices.begin(), + Common::Input::CreateInputDevice); } void EmulatedController::LoadTASParams() { @@ -205,6 +212,46 @@ void EmulatedController::LoadTASParams() { tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3); } +void EmulatedController::LoadVirtualGamepadParams() { + const auto player_index = NpadIdTypeToIndex(npad_id_type); + Common::ParamPackage common_params{}; + common_params.Set("engine", "virtual_gamepad"); + common_params.Set("port", static_cast(player_index)); + for (auto& param : virtual_button_params) { + param = common_params; + } + for (auto& param : virtual_stick_params) { + param = common_params; + } + + // TODO(german77): Replace this with an input profile or something better + virtual_button_params[Settings::NativeButton::A].Set("button", 0); + virtual_button_params[Settings::NativeButton::B].Set("button", 1); + virtual_button_params[Settings::NativeButton::X].Set("button", 2); + virtual_button_params[Settings::NativeButton::Y].Set("button", 3); + virtual_button_params[Settings::NativeButton::LStick].Set("button", 4); + virtual_button_params[Settings::NativeButton::RStick].Set("button", 5); + virtual_button_params[Settings::NativeButton::L].Set("button", 6); + virtual_button_params[Settings::NativeButton::R].Set("button", 7); + virtual_button_params[Settings::NativeButton::ZL].Set("button", 8); + virtual_button_params[Settings::NativeButton::ZR].Set("button", 9); + virtual_button_params[Settings::NativeButton::Plus].Set("button", 10); + virtual_button_params[Settings::NativeButton::Minus].Set("button", 11); + virtual_button_params[Settings::NativeButton::DLeft].Set("button", 12); + virtual_button_params[Settings::NativeButton::DUp].Set("button", 13); + virtual_button_params[Settings::NativeButton::DRight].Set("button", 14); + virtual_button_params[Settings::NativeButton::DDown].Set("button", 15); + virtual_button_params[Settings::NativeButton::SL].Set("button", 16); + virtual_button_params[Settings::NativeButton::SR].Set("button", 17); + virtual_button_params[Settings::NativeButton::Home].Set("button", 18); + virtual_button_params[Settings::NativeButton::Screenshot].Set("button", 19); + + virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0); + virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1); + virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2); + virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3); +} + void EmulatedController::ReloadInput() { // If you load any device here add the equivalent to the UnloadInput() function LoadDevices(); @@ -322,6 +369,35 @@ void EmulatedController::ReloadInput() { }, }); } + + // Use a common UUID for Virtual Gamepad + static constexpr Common::UUID VIRTUAL_UUID = Common::UUID{ + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; + + // Register virtual devices. No need to force update + for (std::size_t index = 0; index < virtual_button_devices.size(); ++index) { + if (!virtual_button_devices[index]) { + continue; + } + virtual_button_devices[index]->SetCallback({ + .on_change = + [this, index](const Common::Input::CallbackStatus& callback) { + SetButton(callback, index, VIRTUAL_UUID); + }, + }); + } + + for (std::size_t index = 0; index < virtual_stick_devices.size(); ++index) { + if (!virtual_stick_devices[index]) { + continue; + } + virtual_stick_devices[index]->SetCallback({ + .on_change = + [this, index](const Common::Input::CallbackStatus& callback) { + SetStick(callback, index, VIRTUAL_UUID); + }, + }); + } } void EmulatedController::UnloadInput() { @@ -349,6 +425,12 @@ void EmulatedController::UnloadInput() { for (auto& stick : tas_stick_devices) { stick.reset(); } + for (auto& button : virtual_button_devices) { + button.reset(); + } + for (auto& stick : virtual_stick_devices) { + stick.reset(); + } camera_devices.reset(); nfc_devices.reset(); } diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index fa7a34278..a398543a6 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -385,6 +385,9 @@ private: /// Set the params for TAS devices void LoadTASParams(); + /// Set the params for virtual pad devices + void LoadVirtualGamepadParams(); + /** * @param use_temporary_value If true tmp_npad_type will be used * @return true if the controller style is fullkey @@ -500,6 +503,12 @@ private: ButtonDevices tas_button_devices; StickDevices tas_stick_devices; + // Virtual gamepad related variables + ButtonParams virtual_button_params; + StickParams virtual_stick_params; + ButtonDevices virtual_button_devices; + StickDevices virtual_stick_devices; + mutable std::mutex mutex; mutable std::mutex callback_mutex; std::unordered_map callback_list; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 7932aaab0..f24c89b04 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(input_common STATIC drivers/udp_client.h drivers/virtual_amiibo.cpp drivers/virtual_amiibo.h + drivers/virtual_gamepad.cpp + drivers/virtual_gamepad.h helpers/stick_from_buttons.cpp helpers/stick_from_buttons.h helpers/touch_from_buttons.cpp diff --git a/src/input_common/drivers/virtual_gamepad.cpp b/src/input_common/drivers/virtual_gamepad.cpp new file mode 100644 index 000000000..7db945aa6 --- /dev/null +++ b/src/input_common/drivers/virtual_gamepad.cpp @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "input_common/drivers/virtual_gamepad.h" + +namespace InputCommon { +constexpr std::size_t PlayerIndexCount = 10; + +VirtualGamepad::VirtualGamepad(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + for (std::size_t i = 0; i < PlayerIndexCount; i++) { + PreSetController(GetIdentifier(i)); + } +} + +void VirtualGamepad::SetButtonState(std::size_t player_index, int button_id, bool value) { + if (player_index > PlayerIndexCount) { + return; + } + const auto identifier = GetIdentifier(player_index); + SetButton(identifier, button_id, value); +} + +void VirtualGamepad::SetButtonState(std::size_t player_index, VirtualButton button_id, bool value) { + SetButtonState(player_index, static_cast(button_id), value); +} + +void VirtualGamepad::SetStickPosition(std::size_t player_index, int axis_id, float x_value, + float y_value) { + if (player_index > PlayerIndexCount) { + return; + } + const auto identifier = GetIdentifier(player_index); + SetAxis(identifier, axis_id * 2, x_value); + SetAxis(identifier, (axis_id * 2) + 1, y_value); +} + +void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value, + float y_value) { + SetStickPosition(player_index, static_cast(axis_id), x_value, y_value); +} + +void VirtualGamepad::ResetControllers() { + for (std::size_t i = 0; i < PlayerIndexCount; i++) { + SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f); + SetStickPosition(i, VirtualStick::Right, 0.0f, 0.0f); + + SetButtonState(i, VirtualButton::ButtonA, false); + SetButtonState(i, VirtualButton::ButtonB, false); + SetButtonState(i, VirtualButton::ButtonX, false); + SetButtonState(i, VirtualButton::ButtonY, false); + SetButtonState(i, VirtualButton::StickL, false); + SetButtonState(i, VirtualButton::StickR, false); + SetButtonState(i, VirtualButton::TriggerL, false); + SetButtonState(i, VirtualButton::TriggerR, false); + SetButtonState(i, VirtualButton::TriggerZL, false); + SetButtonState(i, VirtualButton::TriggerZR, false); + SetButtonState(i, VirtualButton::ButtonPlus, false); + SetButtonState(i, VirtualButton::ButtonMinus, false); + SetButtonState(i, VirtualButton::ButtonLeft, false); + SetButtonState(i, VirtualButton::ButtonUp, false); + SetButtonState(i, VirtualButton::ButtonRight, false); + SetButtonState(i, VirtualButton::ButtonDown, false); + SetButtonState(i, VirtualButton::ButtonSL, false); + SetButtonState(i, VirtualButton::ButtonSR, false); + SetButtonState(i, VirtualButton::ButtonHome, false); + SetButtonState(i, VirtualButton::ButtonCapture, false); + } +} + +PadIdentifier VirtualGamepad::GetIdentifier(std::size_t player_index) const { + return { + .guid = Common::UUID{}, + .port = player_index, + .pad = 0, + }; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h new file mode 100644 index 000000000..3df91cc6f --- /dev/null +++ b/src/input_common/drivers/virtual_gamepad.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A virtual controller that is always assigned to the game input + */ +class VirtualGamepad final : public InputEngine { +public: + enum class VirtualButton { + ButtonA, + ButtonB, + ButtonX, + ButtonY, + StickL, + StickR, + TriggerL, + TriggerR, + TriggerZL, + TriggerZR, + ButtonPlus, + ButtonMinus, + ButtonLeft, + ButtonUp, + ButtonRight, + ButtonDown, + ButtonSL, + ButtonSR, + ButtonHome, + ButtonCapture, + }; + + enum class VirtualStick { + Left = 0, + Right = 1, + }; + + explicit VirtualGamepad(std::string input_engine_); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param player_index the player number that will take this action + * @param button_id the id of the button + * @param value indicates if the button is pressed or not + */ + void SetButtonState(std::size_t player_index, int button_id, bool value); + void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value); + + /** + * Sets the status of all buttons bound with the key to released + * @param player_index the player number that will take this action + * @param axis_id the id of the axis to move + * @param x_value the position of the stick in the x axis + * @param y_value the position of the stick in the y axis + */ + void SetStickPosition(std::size_t player_index, int axis_id, float x_value, float y_value); + void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value, + float y_value); + + /// Restores all inputs into the neutral position + void ResetControllers(); + +private: + /// Returns the correct identifier corresponding to the player index + PadIdentifier GetIdentifier(std::size_t player_index) const; +}; + +} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 942a13535..75b856c95 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -12,6 +12,7 @@ #include "input_common/drivers/touch_screen.h" #include "input_common/drivers/udp_client.h" #include "input_common/drivers/virtual_amiibo.h" +#include "input_common/drivers/virtual_gamepad.h" #include "input_common/helpers/stick_from_buttons.h" #include "input_common/helpers/touch_from_buttons.h" #include "input_common/input_engine.h" @@ -85,6 +86,12 @@ struct InputSubsystem::Impl { Common::Input::RegisterOutputFactory(virtual_amiibo->GetEngineName(), virtual_amiibo_output_factory); + virtual_gamepad = std::make_shared("virtual_gamepad"); + virtual_gamepad->SetMappingCallback(mapping_callback); + virtual_gamepad_input_factory = std::make_shared(virtual_gamepad); + Common::Input::RegisterInputFactory(virtual_gamepad->GetEngineName(), + virtual_gamepad_input_factory); + #ifdef HAVE_SDL2 sdl = std::make_shared("sdl"); sdl->SetMappingCallback(mapping_callback); @@ -132,6 +139,9 @@ struct InputSubsystem::Impl { Common::Input::UnregisterOutputFactory(virtual_amiibo->GetEngineName()); virtual_amiibo.reset(); + Common::Input::UnregisterInputFactory(virtual_gamepad->GetEngineName()); + virtual_gamepad.reset(); + #ifdef HAVE_SDL2 Common::Input::UnregisterInputFactory(sdl->GetEngineName()); Common::Input::UnregisterOutputFactory(sdl->GetEngineName()); @@ -290,6 +300,9 @@ struct InputSubsystem::Impl { if (engine == tas_input->GetEngineName()) { return true; } + if (engine == virtual_gamepad->GetEngineName()) { + return true; + } #ifdef HAVE_SDL2 if (engine == sdl->GetEngineName()) { return true; @@ -338,6 +351,7 @@ struct InputSubsystem::Impl { std::shared_ptr udp_client; std::shared_ptr camera; std::shared_ptr virtual_amiibo; + std::shared_ptr virtual_gamepad; std::shared_ptr keyboard_factory; std::shared_ptr mouse_factory; @@ -347,6 +361,7 @@ struct InputSubsystem::Impl { std::shared_ptr tas_input_factory; std::shared_ptr camera_input_factory; std::shared_ptr virtual_amiibo_input_factory; + std::shared_ptr virtual_gamepad_input_factory; std::shared_ptr keyboard_output_factory; std::shared_ptr mouse_output_factory; @@ -423,6 +438,14 @@ const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const { return impl->virtual_amiibo.get(); } +VirtualGamepad* InputSubsystem::GetVirtualGamepad() { + return impl->virtual_gamepad.get(); +} + +const VirtualGamepad* InputSubsystem::GetVirtualGamepad() const { + return impl->virtual_gamepad.get(); +} + std::vector InputSubsystem::GetInputDevices() const { return impl->GetInputDevices(); } diff --git a/src/input_common/main.h b/src/input_common/main.h index 6218c37f6..1207d786c 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -34,6 +34,7 @@ class Keyboard; class Mouse; class TouchScreen; class VirtualAmiibo; +class VirtualGamepad; struct MappingData; } // namespace InputCommon @@ -108,6 +109,12 @@ public: /// Retrieves the underlying virtual amiibo input device. [[nodiscard]] const VirtualAmiibo* GetVirtualAmiibo() const; + /// Retrieves the underlying virtual gamepad input device. + [[nodiscard]] VirtualGamepad* GetVirtualGamepad(); + + /// Retrieves the underlying virtual gamepad input device. + [[nodiscard]] const VirtualGamepad* GetVirtualGamepad() const; + /** * Returns all available input devices that this Factory can create a new device with. * Each returned ParamPackage should have a `display` field used for display, a `engine` field -- cgit v1.2.3